/**************************************************************************
 * Copyright (C) 2017-2017  Unisound
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 **************************************************************************
 *
 * Description : user_uart_hb1688.c
 * Author      : wufangfang@unisound.com
 * Date        : 2020.04.10
 *
 **************************************************************************/
#include "user_uart_hb1688.h"
#include "user_uart.h"

#define TAG "user_uart_hb1688"

#define HB1688_WAKEUP_LEN   1
#define HB1688_WAKEUP_CODE  0x00
#define HB1688_EXIT_LEN     400
#define HB1688_EXIT_CODE    0x00
#define HB1688_VERSION_LEN  2

#define PACKAGE_START_CODE  0x16

#define CMD_IRIC_VERSION    0x08
#define CMD_ARC_LEN_DATA    0x0c
#define CMD_ARC_SEND_DATA   0x0d
#define CMD_URC_LEN_DATA    0x06
#define CMD_URC_SEND_DATA   0x07
#define CMD_ACK_OK_MASK     0x80
#define CMD_ACK_FAIL_MASK   0xc0

#define PACKAGE_END_CODE    0x08

typedef struct {
  uni_u8    start_code;
  uni_u8    cmd_code;
  uni_u8    data_len;
}__attribute__ ((packed)) hb1688_package_head_t;

typedef struct {
  uni_u8    checksum;
  uni_u8    end_code;
}__attribute__ ((packed)) hb1688_package_tail_t;

typedef struct {
  hb1688_package_head_t head;
  uni_u8                data[HB1688_LRC_LEN];
  hb1688_package_tail_t tail;
}__attribute__ ((packed)) hb1688_recv_package_t;

typedef enum {
  UART_RECV_START_CODE = 0,
  UART_RECV_CMD_CODE,
  UART_RECV_DATE_LEN,
  UART_RECV_DATA,
  UART_RECV_CHECKSUM,
  UART_RECV_END_CODE
}UART_RECV_RUN_STATE;

static _version_recv g_version_recv_cb = NULL;
static _lrc_learn_recv g_arc_recv_cb = NULL;
static _lrc_learn_recv g_urc_recv_cb = NULL;
static UART_RECV_RUN_STATE g_recv_state = UART_RECV_START_CODE;
static hb1688_recv_package_t g_recv_package = {0};
static uni_u8 g_recv_data_len_cur = 0;

uni_u8 _get_checksum_code(hb1688_package_head_t *head,
                                uni_u8 *data, uni_u8 len) {
  uni_u8 *p = (uni_u8 *)head;
  uni_u8 code = 0x00;
  uni_u8 i = 0;
  for (i = 0; i < sizeof(hb1688_package_head_t); i++) {
    code += *p++;
  }
  if (data) {
    for (i = 0; i < len; i++) {
      code += *data++;
    }
  }
  return code % 256;
}

static uni_bool _check_checksum_code(uni_u8 code) {
  uni_u8 tmp_code = _get_checksum_code(&g_recv_package.head, g_recv_package.data,
                                       g_recv_package.head.data_len);
  if (code != tmp_code) {
    LOGE(TAG, "Error chacksum: 0x%02x -- 0x%02x", tmp_code, code);
    return false;
  }
  return true;
}

static void _parser_package(hb1688_recv_package_t *package) {
  uni_u8 cmd_ack = package->head.cmd_code & 0x0f;
  uni_u8 ack_result = package->head.cmd_code & 0xf0;
  uni_bool is_ok = true;
  uni_u16 version = 0x00;
  if (CMD_ACK_OK_MASK == ack_result) {
    LOGT(TAG, "cmd 0x%x ACK success.", cmd_ack);
    is_ok = true;
  } else {
    LOGE(TAG, "cmd 0x%x ACK failed.", cmd_ack);
    is_ok = false;
  }
  switch (cmd_ack) {
    case CMD_IRIC_VERSION:
      if (g_version_recv_cb) {
        uni_memcpy(&version, package->data, sizeof(version));
        g_version_recv_cb(is_ok, version);
      }
      break;
    case CMD_ARC_LEN_DATA:
      if (g_arc_recv_cb) {
        g_arc_recv_cb(is_ok, package->data, package->head.data_len);
      }
      break;
    case CMD_ARC_SEND_DATA:
      break;
    case CMD_URC_LEN_DATA:
      if (g_urc_recv_cb) {
        g_urc_recv_cb(is_ok, package->data, package->head.data_len);
      }
      break;
    case CMD_URC_SEND_DATA:
      break;
  }
}

static void _uart_recv_cb(char *buf, int len) {
  int i = 0;
  uni_u8 read_len = 0;
_GOTO_PARSE_RESET:
  switch (g_recv_state) {
    case UART_RECV_START_CODE:
      /* find start code */
      for (; i < len; i++) {
        if (PACKAGE_START_CODE == buf[i]) {
          break;
        }
      }
      if (i >= len) {
        break;
      }
      g_recv_data_len_cur = 0;
      g_recv_package.head.start_code = buf[i];
      g_recv_state = UART_RECV_CMD_CODE;
      i++;
    case UART_RECV_CMD_CODE:
      if (i >= len) {
        break;
      }
      if (CMD_ACK_OK_MASK != (buf[i] & 0xf0)
        && CMD_ACK_FAIL_MASK != (buf[i] & 0xf0)) {
        g_recv_state = UART_RECV_START_CODE;
        goto _GOTO_PARSE_RESET;
      }
      if (CMD_IRIC_VERSION != (buf[i] & 0x0f)
        && CMD_ARC_LEN_DATA != (buf[i] & 0x0f)
        && CMD_ARC_SEND_DATA != (buf[i] & 0x0f)
        && CMD_URC_LEN_DATA != (buf[i] & 0x0f)
        && CMD_URC_SEND_DATA != (buf[i] & 0x0f)) {
        g_recv_state = UART_RECV_START_CODE;
        goto _GOTO_PARSE_RESET;
      }
      g_recv_package.head.cmd_code = buf[i];
      g_recv_state = UART_RECV_DATE_LEN;
      i++;
    case UART_RECV_DATE_LEN:
      if (i >= len) {
        break;
      }
      if (buf[i] > HB1688_LRC_LEN) {
        g_recv_state = UART_RECV_START_CODE;
        goto _GOTO_PARSE_RESET;
      }
      g_recv_package.head.data_len = buf[i];
      g_recv_state = UART_RECV_DATA;
      i++;
    case UART_RECV_DATA:
      if (i >= len) {
        break;
      }
      read_len = g_recv_package.head.data_len - g_recv_data_len_cur;
      read_len = uni_min(read_len, (len - i));
      uni_memcpy(&g_recv_package.data[g_recv_data_len_cur], buf + i, read_len);
      g_recv_data_len_cur += read_len;
      i += read_len;
      if (g_recv_data_len_cur < g_recv_package.head.data_len) {
        break;
      }
      g_recv_state = UART_RECV_CHECKSUM;
    case UART_RECV_CHECKSUM:
      if (i >= len) {
        break;
      }
      if (!_check_checksum_code(buf[i])) {
        g_recv_state = UART_RECV_START_CODE;
        goto _GOTO_PARSE_RESET;
      }
      g_recv_package.tail.checksum = buf[i];
      g_recv_state = UART_RECV_END_CODE;
      i++;
    case UART_RECV_END_CODE:
      if (i >= len) {
        break;
      }
      if (PACKAGE_END_CODE != buf[i]) {
        g_recv_state = UART_RECV_START_CODE;
        goto _GOTO_PARSE_RESET;
      }
      g_recv_package.tail.end_code = buf[i];
      _parser_package(&g_recv_package);
      g_recv_state = UART_RECV_START_CODE;
      i++;
      goto _GOTO_PARSE_RESET;
    default:
      break;
  }
}

int user_uart_hb1688_init(void) {
  if (0 != user_uart_init(_uart_recv_cb)) {
    LOGE(TAG, "user_uart_init failed.");
    return -1;
  }
  return 0;
}

static int _hb1688_exit(void) {
  char tmp_buf[10] = {HB1688_EXIT_CODE};
  int i = HB1688_EXIT_LEN / 10;
  /* send 400 bytes 0x00 to exit learn or send mode */
  for (; i > 0; i--) {
    user_uart_send(tmp_buf, sizeof(tmp_buf));
  }
  if (HB1688_EXIT_LEN % 10 > 0) {
    user_uart_send(tmp_buf, HB1688_EXIT_LEN % 10);
  }
  return 0;
}

int user_uart_hb1688_final(void) {
  _hb1688_exit();
  user_uart_final();
  return 0;
}

int _uart_send_hb1688_package(uni_u8 cmd, uni_u8 *data, uni_u8 len) {
  hb1688_package_head_t head;
  hb1688_package_tail_t tail;
  char tmp_buf[HB1688_WAKEUP_LEN] = {HB1688_WAKEUP_CODE};
  head.start_code = PACKAGE_START_CODE;
  head.cmd_code = cmd;
  head.data_len = len;
  tail.checksum = _get_checksum_code(&head, data, len);
  tail.end_code = PACKAGE_END_CODE;
  /* send 1 byte 0x00 to wakeup HB1688 */
  user_uart_send(tmp_buf, sizeof(tmp_buf));
  /* send head */
  user_uart_send((char *)&head, sizeof(head));
  /* send data */
  if ((len > 0) && data) {
    user_uart_send((char *)data, len);
  }
  /* send tail */
  user_uart_send((char *)&tail, sizeof(tail));
  return 0;
}

int user_uart_hb1688_get_version(_version_recv recv_cb) {
  g_version_recv_cb = recv_cb;
  return _uart_send_hb1688_package(CMD_IRIC_VERSION, NULL, 0);
}

int user_uart_hb1688_arc_learn(_lrc_learn_recv recv_cb) {
  g_arc_recv_cb = recv_cb;
  return _uart_send_hb1688_package(CMD_ARC_LEN_DATA, NULL, 0);
}

int user_uart_hb1688_urc_learn(_lrc_learn_recv recv_cb) {
  g_urc_recv_cb = recv_cb;
  return _uart_send_hb1688_package(CMD_URC_LEN_DATA, NULL, 0);
}

int user_uart_hb1688_learn_exit(void) {
  g_urc_recv_cb = NULL;
  g_arc_recv_cb = NULL;
  return _hb1688_exit();
}

int user_uart_hb1688_arc_send(uni_u8 *buf, uni_u8 len) {
  return _uart_send_hb1688_package(CMD_ARC_SEND_DATA, buf, len);
}

int user_uart_hb1688_urc_send(uni_u8 *buf, uni_u8 len) {
  return _uart_send_hb1688_package(CMD_URC_SEND_DATA, buf, len);
}

int user_uart_hb1688_send_exit(void) {
  return _hb1688_exit();
}

